package com.example.demo.nio.chat_room;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

public class CRServer {
    public static void main(String[] args) {

        try {
            // 获得一个服务端SocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); // 设置非阻塞
            serverSocketChannel.socket().bind(new InetSocketAddress(7000)); // 设置端口
            Selector selector = Selector.open(); // 获得选择器
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册

            // 轮询
            while (true) {
                selector.select(); // 获取触发事件
                // 获得所有就绪事件并交给迭代器
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                // 迭代器遍历
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    keyHandler(key);
                    iterator.remove(); // 遍历完记得清理本次事件
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 事件处理器
     * @param key 事件内容
     */
    private static void keyHandler(SelectionKey key) {

        // 处理连接事件
        if (key.isAcceptable()) {
            try {
                // 获得各种Channel并做处理
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                SocketChannel socketChannel = channel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(key.selector(), SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                System.out.println("客户端成功连接,地址："+socketChannel.getRemoteAddress());
                // 连接成功，返回消息
                socketChannel.write(ByteBuffer.wrap("欢迎来到聊天室".getBytes()));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        // 处理读入事件
        else if (key.isReadable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

            try {
                int len =channel.read(byteBuffer);
                byteBuffer.flip();// 把指针位置反转好

                // 如果正常断开,则会默认发一个写入事件给服务端,那么这里就会收到并读取出-1
                if(len==-1||"断开连接".equals(new String(byteBuffer.array(),0,len,StandardCharsets.UTF_8))){
                    System.out.println("有一个客户端正常断开了,它的地址是："+channel.getRemoteAddress());
                    channel.close();
                    key.cancel();
                    len=-1;
                }
                // 其实这里1024的字节容纳字数差不多300汉字多一点,聊天足够了,没必要用while
                // 但是如果是文件之类的话，就需要这样了,所以就这样写着了
                while (len>0) {
                    // 打印记录一下,其实就是收集数据记录
                    System.out.println(channel.getRemoteAddress()+":"+new String(byteBuffer.array(),0,len,StandardCharsets.UTF_8).trim());
                    // 如果是多次读入的话这个就放在外面即可，多放一个中转变量收数据传给他就好
                    broadcast(key, byteBuffer);
                    // 如果要多次读取的话就必须即使清理缓冲区了
                    byteBuffer.clear();
                    // 重新读取后续的数据
                    len=channel.read(byteBuffer);
                }
            } catch (IOException e) {
                try {
                    // 如果出现意外断开,记得关闭资源
                    System.out.println("有一个客户端突然寄了,它的地址是："+channel.getRemoteAddress());
                    channel.close();
                    key.cancel();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    /**
     * 广播消息
     * @param selectionKey 事件标识
     * @param buffer 数据包
     */
    private static void broadcast(SelectionKey selectionKey, ByteBuffer buffer) {
        Selector selector = selectionKey.selector();

        // 通过选择器拿到所有注册过的通道key,然后用迭代器遍历
        // 其他的一目了然,就不写注释了
        Iterator<SelectionKey> iterator = selector.keys().iterator();
        while(iterator.hasNext()){
            SelectionKey key = iterator.next();

            // 要注意包括服务端的ServerSocketChannel也会被上面的selector.keys()拿到
            // 所以这里要做 instanceof 类型判断
            if(key.channel() instanceof SocketChannel&& key.channel()!= selectionKey.channel()){
                try {
                    ((SocketChannel) key.channel()).write(buffer);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

        }

    }
}